package gitbucket.core.util

import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib._
import org.eclipse.jgit.revwalk._
import org.eclipse.jgit.treewalk._
import org.eclipse.jgit.merge._
import org.eclipse.jgit.errors._

import java.nio.file._
import java.io.File
import scala.util.Using

object GitSpecUtil {

    def withTestFolder[U](f: File => U): U = {
        val folder = new File(System.getProperty("java.io.tmpdir"), "test-" + System.nanoTime)
        if (!folder.mkdirs()) {
            throw new java.io.IOException("can't create folder " + folder.getAbsolutePath)
        }
        try { f(folder) }
        finally { FileUtils.deleteQuietly(folder) }
    }

    def withTestRepository[U](f: Git => U): U =
        withTestFolder(folder => Using.resource(Git.open(createTestRepository(folder)))(f))

    def createTestRepository(dir: File): File = {
        RepositoryCache.clear()
        FileUtils.deleteQuietly(dir)
        Files.createDirectories(dir.toPath())
        JGitUtil.initRepository(dir, "main")
        dir
    }

    def createFile(
        git: Git,
        branch: String,
        name: String,
        content: String,
        authorName: String = "dummy",
        authorEmail: String = "dummy@example.com",
        message: String = "test commit"
    ): ObjectId = {
        val builder = DirCache.newInCore.builder()
        val inserter = git.getRepository.newObjectInserter()
        val headId = git.getRepository.resolve(branch + "^{commit}")
        if (headId != null) {
            JGitUtil.processTree(git, headId) { (path, tree) =>
                if (name != path) {
                    builder.add(
                      JGitUtil
                          .createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)
                    )
                }
            }
        }
        builder.add(JGitUtil.createDirCacheEntry(
          name,
          FileMode.REGULAR_FILE,
          inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))
        ))
        builder.finish()
        val commitId = JGitUtil.createNewCommit(
          git,
          inserter,
          headId,
          builder.getDirCache.writeTree(inserter),
          branch,
          authorName,
          authorEmail,
          message
        )
        inserter.flush()
        inserter.close()
        commitId
    }

    def getFile(git: Git, branch: String, path: String) = {
        val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
        val objectId = Using.resource(new TreeWalk(git.getRepository)) { walk =>
            walk.addTree(revCommit.getTree)
            walk.setRecursive(true)
            @scala.annotation.tailrec
            def _getPathObjectId: ObjectId = walk.next match {
                case true if (walk.getPathString == path) => walk.getObjectId(0)
                case true                                 => _getPathObjectId
                case false => throw new Exception(s"not found ${branch} / ${path}")
            }
            _getPathObjectId
        }
        JGitUtil.getContentInfo(git, path, objectId, true)
    }

    def mergeAndCommit(git: Git, into: String, branch: String, message: String = null): Unit = {
        val repository = git.getRepository
        val merger = MergeStrategy.RECURSIVE.newMerger(repository, true)
        val mergeBaseTip = repository.resolve(into)
        val mergeTip = repository.resolve(branch)
        val conflicted =
            try { !merger.merge(mergeBaseTip, mergeTip) }
            catch { case e: NoMergeBaseException => true }
        if (conflicted) { throw new RuntimeException("conflict!") }
        val mergeTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeTip))
        val committer = mergeTipCommit.getCommitterIdent
        // creates merge commit
        val mergeCommit = new CommitBuilder()
        mergeCommit.setTreeId(merger.getResultTreeId)
        mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip)*)
        mergeCommit.setAuthor(committer)
        mergeCommit.setCommitter(committer)
        mergeCommit.setMessage(message)
        // insertObject and got mergeCommit Object Id
        val inserter = repository.newObjectInserter
        val mergeCommitId = inserter.insert(mergeCommit)
        inserter.flush()
        inserter.close()
        // update refs
        val refUpdate = repository.updateRef(into)
        refUpdate.setNewObjectId(mergeCommitId)
        refUpdate.setForceUpdate(true)
        refUpdate.setRefLogIdent(committer)
        refUpdate.update()
    }
}
